Goal
JD has done some great work and I will see if I can continue in his direction. As JD pointed out in his initial analysis that time-dependence needs to be examined. I will explore the PREC variable and perform some exploratory analysis with the goal in mind to reduce the overall dimensions. My thought here is to simply look for observations that standout - outliers. This will then allow us to focus our analysis on a particuliar region of interest (instead of picking an arbitrary region at random), while simplifying our analysis, and saving our computers from a computational meltdown!
Housekeeping
Before I start on that, I have to modify the initial function I wrote getCDF.data. Although it works in its current state, it is not particuliarly efficient and I am tired of waiting for my computer to crunch numbers! The current version of getCDF.data has one additional parameter, asCube which is a logical. The additional parameter allows the function to return the data retrieved as either a data.frame or an array. The data.frame works the same as in the first function version. The asCube array is able to perform calculations at lighting speeds and is the solution for us to be able to work with this hugh dataset.
I will demonstrate the function in action below.
file.maxt <- "../../data/era-interim/MAXT.nc" # Maximum temperature
source("load_netcdf.R")
source("../../r/helpers.R")
maxt.cube <- getCDF.data(filePath = file.maxt,
lonRange = c(223.5,301.5),
latRange = c(17.25,55.5),
timeRange = c(0, 14244),
asCube = TRUE)
maxt.by.year <- array(dim = c(105, 52, 39))
for (i in 1979:2017) {
begin <- paste(i, 1, 1, sep = "-")
end <- paste(i, 12, 31, sep = "-")
## Helper function to get "days since" range
range <- getTimeRange(begin, end, "1979-01-01")
## Stores yearly average of MAXT in array.
## NOTE: The second argument for the apply function takes on several variations as follows:
## 1 - apply function rowwise (lon)
## 2 - apply function columnwise (lat)
## 1:2 - apply function both rowwise and columnwise (lon, lat)
maxt.by.year[, , i - 1979 + 1] <- apply(maxt.cube$mets$MAXT[, , range[1]:range[2]], 1:2, mean)
}
## Add dimension names for readability
dimnames(maxt.by.year) <- list(maxt.cube$dims$lon,
maxt.cube$dims$lat,
1979:2017)
## Retrieve mean MAXT for 1988
maxt.by.year[1:6, 1:6 , "1988"] %>% head()
17.25 18 18.75 19.5 20.25 21
223.5 297.1110 296.6953 296.3285 295.9486 295.5955 295.2523
224.25 297.0590 296.6553 296.2675 295.8677 295.5032 295.1486
225 297.0400 296.6190 296.2056 295.7903 295.4095 295.0448
225.75 297.0134 296.5907 296.1557 295.7282 295.3207 294.9488
226.5 296.9965 296.5584 296.1016 295.6662 295.2433 294.8509
227.25 296.9713 296.5252 296.0568 295.6065 295.1677 294.7530
## Since memory resources are precious, I suggest loading only one data
## cube at a time and unloading it once done with. Can always reload later.
rm(maxt.cube)
## Trigger garbage collection to retrieve unused memory
gc()
used (Mb) gc trigger (Mb) max used (Mb)
Ncells 1952591 104.3 3545321 189.4 3545321 189.4
Vcells 4285612 32.7 154994232 1182.6 192454360 1468.4
As you can see the getCDF.data function call remains essentially the same. The difference is how we perform our calculations. The flexibility is apparent. For example, what if we wanted to know the cumulative precipation for a years worth?
file.prec <- "../../data/era-interim/PREC.nc" # Precipitation
prec.cube <- getCDF.data(filePath = file.prec,
lonRange = c(223.5,301.5),
latRange = c(17.25,55.5),
timeRange = c(0, 14244),
asCube = TRUE)
prec.by.year <- array(dim = c(105, 52, 39))
for (i in 1979:2017) {
begin <- paste(i, 1, 1, sep = "-")
end <- paste(i, 12, 31, sep = "-")
## Helper function to get "days since" range
range <- getTimeRange(begin, end, "1979-01-01")
prec.by.year[, , i - 1979 + 1] <- apply(prec.cube$mets$PREC[, , range[1]:range[2]], 1:2, sum)
}
## Add dimension names for readability
dimnames(prec.by.year) <- list(prec.cube$dims$lon,
prec.cube$dims$lat,
1979:2017)
## Retrieve total PREC for 1988
prec.by.year[1:6, 1:6 , "1988"] %>% head()
17.25 18 18.75 19.5 20.25 21
223.5 0.5868086 0.5337014 0.4828378 0.4178053 0.3680781 0.3749526
224.25 0.5575477 0.5091768 0.4681884 0.4157781 0.3507933 0.3444483
225 0.5472542 0.4996663 0.4456804 0.4025582 0.3481106 0.3364098
225.75 0.5160724 0.4548569 0.4171385 0.3828071 0.3490900 0.3239893
226.5 0.4736967 0.4079036 0.3858029 0.3574364 0.3405856 0.3243647
227.25 0.4474414 0.3815528 0.3456028 0.3328639 0.3211995 0.3203895
What about monthly total precipitation?
library(lubridate)
prec.by.month <- array(dim = c(105, 52, 468))
for (i in 1:468) {
year <- ceiling(i / 12) - 1
month <- ifelse(i %% 12 == 0, 12, i %% 12)
begin <- paste(year + 1979,
month, 1,
sep = "-")
end <- paste(year + 1979,
month, days_in_month(as.Date(paste(year + 1979, month, 1, sep = "-"))),
sep = "-")
range <- getTimeRange(begin, end, "1979-01-01")
## Store monthly precipitation data
prec.by.month[, , i] <- apply(prec.cube$mets$PREC[, , range[1]:range[2]], 1:2, sum)
}
dimnames(prec.by.month) <- list(prec.cube$dims$lon,
prec.cube$dims$lat,
1:468)
## Retrieve month total PREC for May 1988
prec.by.month[1:6, 1:6 , (1988 - 1979) * 12 + 5] %>% head()
17.25 18 18.75 19.5 20.25 21
223.5 0.01762699 0.02317333 0.02644928 0.02640685 0.02852388 0.03023329
224.25 0.01619278 0.02260180 0.02517910 0.02612115 0.02731180 0.02633819
225 0.01637277 0.01987095 0.02368143 0.02493039 0.02610518 0.02473982
225.75 0.01583834 0.01792347 0.02189790 0.02470273 0.02479804 0.02322623
226.5 0.01466858 0.01681726 0.02124692 0.02269703 0.02259119 0.02129458
227.25 0.01377955 0.01515551 0.01887071 0.02087648 0.02098757 0.01971747
rm(prec.cube); gc()
used (Mb) gc trigger (Mb) max used (Mb)
Ncells 1952649 104.3 3545321 189.4 3545321 189.4
Vcells 7054177 53.9 155205765 1184.2 194007102 1480.2
Lets plot a raster map of 1988 for average maximum temperature and total precipitation.
library(tidyverse)
library(ggplot2)
library(ggthemes)
library(reshape2)
## Convert arrays into long form for plotting
prec.long <- prec.by.year %>% melt() %>% rename(lon = Var1, lat = Var2, year = Var3, total.prec = value)
maxt.long <- maxt.by.year %>% melt() %>% rename(lon = Var1, lat = Var2, year = Var3, mean.maxt = value)
ggplot() +
geom_raster(data = maxt.long %>% filter(year == 1988),
aes(x=lon-360,y=lat,fill=mean.maxt), interpolate = TRUE) +
geom_contour(data = prec.long %>% filter(year == 1988),
aes(x=lon-360,y=lat,z=total.prec), color="white", alpha = 0.33) +
labs(fill = "MAXT",
title = "Mean MAXT and Total PREC - 1988") +
conus.map() +
theme_map()

Precipitation (PREC) Exploration with Time-Dependence
Here is where I will try to reduce the dimensions of our dataset by fitting a time series model at each spatial location (lon, lat) from 1979 to 2017 of monthly total precipitation and search for irregularities in the data by way of outliers. I will standardize the residuals and compare the entire spatial-temporal data for outliers. This will identify regions of anomalies sliced by time (months).
library(tidyverse)
library(reshape2)
library(forecast)
file.prec <- "../../data/era-interim/PREC.nc" # Precipitation
prec.cube <- getCDF.data(filePath = file.prec,
lonRange = c(223.5,301.5),
latRange = c(17.25,55.5),
timeRange = c(0, 14244),
asCube = TRUE)
## Array to store residuals from time-series decomposition
prec.month.ts <- array(dim = c(105, 52, 468))
## Add dimension names for readability
dimnames(prec.month.ts) <- list(prec.cube$dims$lon,
prec.cube$dims$lat,
1:468)
rm(prec.cube)
gc()
used (Mb) gc trigger (Mb) max used (Mb)
Ncells 1952062 104.3 3545321 189.4 3545321 189.4
Vcells 9860512 75.3 159330896 1215.6 194007102 1480.2
for (lon in 1:105) {
for (lat in 1:52) {
## Format data into time-series
ts.data <- ts(prec.by.month[lon, lat, ], start = c(1979, 1), frequency = 12)
## Decompose time-series into seasonal, trend, and random components
ts.model <- stl(ts.data, s.window = "periodic")
## Retrieve model residuals and store in array
prec.month.ts[lon, lat, ] <- remainder(ts.model) %>% as.vector()
}
}
## Standard deviation for spatial points across time
prec.month.ts.sd <- apply(prec.month.ts, 1:2, sd)
## Rescale all residuals
for (lon in 1:105) {
for (lat in 1:52) {
prec.month.ts[lon, lat, ] <- prec.month.ts[lon, lat, ] / prec.month.ts.sd[lon, lat]
}
}
prec.month.ts.sd.long <- prec.month.ts %>% melt() %>% rename(lon = Var1,
lat = Var2,
time = Var3,
sd.residual = value)
## Interested in residuals that are greater than 3 sd of the spatial-temporal data
outliers <- prec.month.ts.sd.long %>% filter(abs(sd.residual) > 3)
outliers %>% head()
NA
Let see if our efforts captured any known anomalies. I will plot outliers from 2012 and see if we can “see” Hurricane Sandy.
library(ggplot2)
library(ggthemes)
## (12*1:39) - 12
outliers <- outliers %>% mutate(year = ceiling(time / 12) - 1 + 1979,
month = ifelse(time %% 12 == 0, 12, time %% 12))
outliers.2012 <- outliers %>% filter(year == 2012)
## Helper function to decide number of clusters. Similiar to a scree plot
wssplot(outliers.2012[, c("lon", "lat")])

## Create arbitrary clusters based on spatial points. More clusters results in smaller
## spatial regions and less clusters results in larger spatial regions
set.seed(1234)
cluster <- kmeans(outliers.2012[, c("lon", "lat")], centers = 5, nstart = 25)
## Assign spatial points to cluster
outliers.2012$cluster <- cluster$cluster
## Retrieve center of cluster
centers <- cluster$centers %>% as.data.frame()
ggplot(data = outliers.2012, aes(x = lon - 360, y = lat)) +
stat_density2d(aes(fill = factor(cluster), group = factor(cluster)), geom = "polygon") +
geom_point(aes(color = sd.residual), size = 4) +
geom_label(data = centers,
aes(x = lon - 360, y = lat, label = 1:5),
size = 6) +
scale_color_distiller(palette = "Spectral") +
labs(fill = "Cluster",
color = "Standardized Residuals",
title = "Cluster(s) of Anomalies - 2012") +
conus.map() +
theme_map()

From the map above, it looks like cluster 2 could be the result of Hurricane Sandy leaving behind an unusual amount of precipitation. Keep in mind that the map plotted anomalies for the entire year - possibly anomalies before Sandy. Since our analysis has been on a monthly scale, lets take a look of each month for 2012.
ggplot(data = outliers.2012, aes(x = lon - 360, y = lat)) +
geom_point(aes(color = sd.residual, pch = factor(cluster)), size = 4, show.legend = FALSE) +
scale_color_distiller(palette = "Spectral") +
facet_wrap(~month) +
conus.map() +
labs(title = "Cluster(s) of Anomalies by Month (2012)") +
theme_map()

## October 2012
ggplot(data = outliers.2012 %>% filter(month == 10), aes(x = lon - 360, y = lat)) +
geom_tile(aes(fill = sd.residual), size = 4) +
geom_label(data = centers,
aes(x = lon - 360, y = lat, label = 1:5),
size = 6) +
scale_fill_distiller(palette = "Reds") +
conus.map() +
labs(x = "Longitude",
y = "Latitude",
fill = "Standardized Residuals",
title = "Cluster(s) of Anomalies, October 2012") +
theme_map()

There it is! At time 406 (October 2012) we captured Hurricane Sandy’s unusual amount of precipitation and according to my Google search Hurricane Sandy officially made landfall October 29, 2012. I checked the path Hurricane Sandy took and it was, according to my map, from cluster 5 hugging the eastcoast to cluster 2. You can see that Hurricane Sandy dumped tremendous amounts of rain over cluster 5 working its way up towards cluster 2 and not dumping any unusual amount of precipitation in between. I would guess this is where Sandy picked up energy, ultimately dumping everything it had on New Jersey.
As demonstrated above, we were able to capture an unusual event, Hurricane Sandy, by way of anomaly detection. My next question would be, what other known anomalies did we pick up? Or an even more interesting question would be what other anomalies did we pick up that is unknown? What effect did this unknown anomaly have on the region? Was this unknown event rare? Or does this unknown event repeat on a cycle?
By finding these extremes in the data, we can use JD’s smoothing model with additional data, for example, water quality for that region and analyze how these extreme events effect water quality at that region. Can we show that unusually low amounts of precipitation (drought) negatively effect water quality by concentrating contaminants and other particles? Or how about the other extreme? What can we say about a region that is saturated from precipitation? Ultimately, I believe the questions we answer that gives us the best chance of winning are questions that directly effect normal people’s lives. Questions and answers that everyone can relate to.
Regions of Interest for Investigation
The following map I am plotting collapses all the previously detected outliers onto a single two-dimensional map.
outliers.summary <- outliers %>% group_by(lon, lat) %>% summarise(anomalies = n(),
mean = mean(sd.residual),
max = max(abs(sd.residual)))
plot.summary <-
ggplot(data = outliers.summary, aes(x = lon - 360, y = lat)) +
geom_raster(aes(fill = mean), interpolate = TRUE) +
geom_text(aes(label = anomalies, size = factor(anomalies)), show.legend = FALSE) +
scale_size_manual(values = exp(seq(0.75, 2.25, length.out = 15)) - 1) +
scale_fill_distiller(palette = "RdBu") +
labs(x = "Longitude",
y = "Latitude",
fill = "Mean Residual (strength)",
title = "Anomaly Count and Strength (1979-2017)") +
conus.map() +
theme_minimal()
ggsave("anomalies-plot.jpg", plot.summary, height = 20, width = 30, units = "in")
The map is hard to see when outputed in this document, so I saved it as anomalies-plot.jpg in this file directory.
How I interpret the map is as follows: the numbers you see is a simple count of anomalies in the 39 years at that particuliar spatial point and the different sizes of the numbers just correspond to the count. It helps us see larger numbers over smaller numbers. The colors in the raster map can be interpreted as the overall strength of the anomalies. The “reds” tells us that when an anomaly occurs, the anomaly releases unusually high amounts of precipitation for that location. The “blues” tell us the opposite, indicating the anomalies over these locations releases unusually low amounts of precipitation.
The above map has alot going on so I will try to simplify the map even more by creating arbitrary ranks for each of the spatial points as a weighted aggregrate of anomalies, mean residual (strength), and max residual. The weights are as follows: anomalies = 0.60, strength = 0.30, and max strength = 0.10.
outliers.summary <- outliers.summary %>% mutate(rank = anomalies * 0.60 + abs(mean) * 0.30 + abs(max) * 0.10)
spatial.points <- prec.month.ts.sd.long %>% group_by(lon, lat) %>% nest() %>% select(lon, lat)
anomaly.points <- outliers.summary %>% group_by(lon, lat) %>% nest() %>% select(lon, lat)
void <- anti_join(spatial.points, anomaly.points)
ggplot(data = outliers.summary, aes(x = lon - 360, y = lat)) +
geom_raster(aes(fill = rank), interpolate = TRUE) +
geom_tile(data = void, fill = "black") +
scale_fill_distiller(palette = "Reds") +
labs(x = "Longitude",
y = "Latitude",
fill = "Rank",
title = "Spatial Anomaly Strength by Rank (1979-2017)") +
conus.map() +
theme_minimal()

At last, we are guided by the bright areas on the map as potential areas of interest for further research. For example, the border of southern California, Nevada, Arizona, and Baja Mexico lights up like a beacon. Another region is central Missouri or most of Montana. There are numerous other locations for us to consider. The black spots on the map are voids (no detected anomaly).
Afterwards, I took the “Spatial Anomaly Strength by Rank (1979-2017)” plot above and post processed the image by adjusting the contrast and brightness levels to bring the bright spots out. I saved two processed images in this file directory (anomaly-map-processed-1 and anomaly-map-processed-2)
Processed Images
If we wanted to, we could seperate the outliers.summary by unusually low and unusually high precipitation, re-rank the anomalies, and replot the heat map.
Conclusion
The goal I set out at the beginning of this report was to reduce the spatial dimension of our dataset to allow for targeted analysis. I believe the analysis above will guide us in subsetting the CONUS map. An after thought that I think would be insightful is to superimpose the “Spatial Anomaly Strength by Rank (1979-2017)” plot over a terrain map, rivers and lakes, national parks, highways, city, etc. This could provide some guidance in region selection. The above analysis of the PREC variable could be easily swapped out for MAXT or MINT and reanalyzed. I hope this will help guide us moving forward with targeted analysis and additional data collection efforts.
LS0tDQp0aXRsZTogIkV4cGxvcmF0b3J5IEFuYWx5c2lzIChjb250aW51YXRpb24pIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyMjIEdvYWwNCg0KSkQgaGFzIGRvbmUgc29tZSBncmVhdCB3b3JrIGFuZCBJIHdpbGwgc2VlIGlmIEkgY2FuIGNvbnRpbnVlIGluIGhpcyBkaXJlY3Rpb24uIEFzIEpEIHBvaW50ZWQgb3V0IGluIGhpcyBpbml0aWFsIGFuYWx5c2lzIHRoYXQgdGltZS1kZXBlbmRlbmNlIG5lZWRzIHRvIGJlIGV4YW1pbmVkLiBJIHdpbGwgZXhwbG9yZSB0aGUgYFBSRUNgIHZhcmlhYmxlIGFuZCBwZXJmb3JtIHNvbWUgZXhwbG9yYXRvcnkgYW5hbHlzaXMgd2l0aCB0aGUgZ29hbCBpbiBtaW5kIHRvIHJlZHVjZSB0aGUgb3ZlcmFsbCBkaW1lbnNpb25zLiBNeSB0aG91Z2h0IGhlcmUgaXMgdG8gc2ltcGx5IGxvb2sgZm9yIG9ic2VydmF0aW9ucyB0aGF0IHN0YW5kb3V0IC0gb3V0bGllcnMuIFRoaXMgd2lsbCB0aGVuIGFsbG93IHVzIHRvIGZvY3VzIG91ciBhbmFseXNpcyBvbiBhIHBhcnRpY3VsaWFyIHJlZ2lvbiBvZiBpbnRlcmVzdCAoaW5zdGVhZCBvZiBwaWNraW5nIGFuIGFyYml0cmFyeSByZWdpb24gYXQgcmFuZG9tKSwgd2hpbGUgc2ltcGxpZnlpbmcgb3VyIGFuYWx5c2lzLCBhbmQgc2F2aW5nIG91ciBjb21wdXRlcnMgZnJvbSBhIGNvbXB1dGF0aW9uYWwgbWVsdGRvd24hIA0KDQoqKioNCg0KIyMjIyBIb3VzZWtlZXBpbmcNCg0KQmVmb3JlIEkgc3RhcnQgb24gdGhhdCwgSSBoYXZlIHRvIG1vZGlmeSB0aGUgaW5pdGlhbCBmdW5jdGlvbiBJIHdyb3RlIGBnZXRDREYuZGF0YWAuIEFsdGhvdWdoIGl0IHdvcmtzIGluIGl0cyBjdXJyZW50IHN0YXRlLCBpdCBpcyBub3QgcGFydGljdWxpYXJseSBlZmZpY2llbnQgYW5kIEkgYW0gdGlyZWQgb2Ygd2FpdGluZyBmb3IgbXkgY29tcHV0ZXIgdG8gY3J1bmNoIG51bWJlcnMhIFRoZSBjdXJyZW50IHZlcnNpb24gb2YgYGdldENERi5kYXRhYCBoYXMgb25lIGFkZGl0aW9uYWwgcGFyYW1ldGVyLCBgYXNDdWJlYCB3aGljaCBpcyBhIGxvZ2ljYWwuIFRoZSBhZGRpdGlvbmFsIHBhcmFtZXRlciBhbGxvd3MgdGhlIGZ1bmN0aW9uIHRvIHJldHVybiB0aGUgZGF0YSByZXRyaWV2ZWQgYXMgZWl0aGVyIGEgZGF0YS5mcmFtZSBvciBhbiBhcnJheS4gVGhlIGRhdGEuZnJhbWUgd29ya3MgdGhlIHNhbWUgYXMgaW4gdGhlIGZpcnN0IGZ1bmN0aW9uIHZlcnNpb24uIFRoZSBgYXNDdWJlYCBhcnJheSBpcyBhYmxlIHRvIHBlcmZvcm0gY2FsY3VsYXRpb25zIGF0IGxpZ2h0aW5nIHNwZWVkcyBhbmQgaXMgdGhlIHNvbHV0aW9uIGZvciB1cyB0byBiZSBhYmxlIHRvIHdvcmsgd2l0aCB0aGlzIGh1Z2ggZGF0YXNldC4gDQoNCkkgd2lsbCBkZW1vbnN0cmF0ZSB0aGUgZnVuY3Rpb24gaW4gYWN0aW9uIGJlbG93Lg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZmlsZS5tYXh0IDwtICIuLi8uLi9kYXRhL2VyYS1pbnRlcmltL01BWFQubmMiICMgTWF4aW11bSB0ZW1wZXJhdHVyZQ0KDQpzb3VyY2UoImxvYWRfbmV0Y2RmLlIiKQ0Kc291cmNlKCIuLi8uLi9yL2hlbHBlcnMuUiIpDQoNCm1heHQuY3ViZSA8LSBnZXRDREYuZGF0YShmaWxlUGF0aCA9IGZpbGUubWF4dCwNCiAgICAgICAgICAgICAgICAgICAgICAgICBsb25SYW5nZSA9IGMoMjIzLjUsMzAxLjUpLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgbGF0UmFuZ2UgPSBjKDE3LjI1LDU1LjUpLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgdGltZVJhbmdlID0gYygwLCAxNDI0NCksDQogICAgICAgICAgICAgICAgICAgICAgICAgYXNDdWJlID0gVFJVRSkNCg0KbWF4dC5ieS55ZWFyIDwtIGFycmF5KGRpbSA9IGMoMTA1LCA1MiwgMzkpKQ0KDQpmb3IgKGkgaW4gMTk3OToyMDE3KSB7DQogIGJlZ2luIDwtIHBhc3RlKGksIDEsIDEsIHNlcCA9ICItIikNCiAgZW5kIDwtIHBhc3RlKGksIDEyLCAzMSwgc2VwID0gIi0iKQ0KICANCiAgIyMgSGVscGVyIGZ1bmN0aW9uIHRvIGdldCAiZGF5cyBzaW5jZSIgcmFuZ2UgIA0KICByYW5nZSA8LSBnZXRUaW1lUmFuZ2UoYmVnaW4sIGVuZCwgIjE5NzktMDEtMDEiKSANCiAgIyMgU3RvcmVzIHllYXJseSBhdmVyYWdlIG9mIE1BWFQgaW4gYXJyYXkuIA0KICAjIyBOT1RFOiBUaGUgc2Vjb25kIGFyZ3VtZW50IGZvciB0aGUgYXBwbHkgZnVuY3Rpb24gdGFrZXMgb24gc2V2ZXJhbCB2YXJpYXRpb25zIGFzIGZvbGxvd3M6DQogICMjIDEgLSBhcHBseSBmdW5jdGlvbiByb3d3aXNlIChsb24pDQogICMjIDIgLSBhcHBseSBmdW5jdGlvbiBjb2x1bW53aXNlIChsYXQpDQogICMjIDE6MiAtIGFwcGx5IGZ1bmN0aW9uIGJvdGggcm93d2lzZSBhbmQgY29sdW1ud2lzZSAobG9uLCBsYXQpDQogIG1heHQuYnkueWVhclssICwgaSAtIDE5NzkgKyAxXSA8LSBhcHBseShtYXh0LmN1YmUkbWV0cyRNQVhUWywgLCByYW5nZVsxXTpyYW5nZVsyXV0sIDE6MiwgbWVhbikNCn0NCg0KIyMgQWRkIGRpbWVuc2lvbiBuYW1lcyBmb3IgcmVhZGFiaWxpdHkNCmRpbW5hbWVzKG1heHQuYnkueWVhcikgPC0gbGlzdChtYXh0LmN1YmUkZGltcyRsb24sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4dC5jdWJlJGRpbXMkbGF0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDE5Nzk6MjAxNykNCg0KIyMgUmV0cmlldmUgbWVhbiBNQVhUIGZvciAxOTg4DQptYXh0LmJ5LnllYXJbMTo2LCAxOjYgLCAiMTk4OCJdICU+JSBoZWFkKCkNCg0KIyMgU2luY2UgbWVtb3J5IHJlc291cmNlcyBhcmUgcHJlY2lvdXMsIEkgc3VnZ2VzdCBsb2FkaW5nIG9ubHkgb25lIGRhdGEgDQojIyBjdWJlIGF0IGEgdGltZSBhbmQgdW5sb2FkaW5nIGl0IG9uY2UgZG9uZSB3aXRoLiBDYW4gYWx3YXlzIHJlbG9hZCBsYXRlci4NCnJtKG1heHQuY3ViZSkNCiMjIFRyaWdnZXIgZ2FyYmFnZSBjb2xsZWN0aW9uIHRvIHJldHJpZXZlIHVudXNlZCBtZW1vcnkNCmdjKCkNCmBgYA0KDQpBcyB5b3UgY2FuIHNlZSB0aGUgYGdldENERi5kYXRhYCBmdW5jdGlvbiBjYWxsIHJlbWFpbnMgZXNzZW50aWFsbHkgdGhlIHNhbWUuIFRoZSBkaWZmZXJlbmNlIGlzIGhvdyB3ZSBwZXJmb3JtIG91ciBjYWxjdWxhdGlvbnMuIFRoZSBmbGV4aWJpbGl0eSBpcyBhcHBhcmVudC4gRm9yIGV4YW1wbGUsIHdoYXQgaWYgd2Ugd2FudGVkIHRvIGtub3cgdGhlIGN1bXVsYXRpdmUgcHJlY2lwYXRpb24gZm9yIGEgeWVhcnMgd29ydGg/DQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpmaWxlLnByZWMgPC0gIi4uLy4uL2RhdGEvZXJhLWludGVyaW0vUFJFQy5uYyIgIyBQcmVjaXBpdGF0aW9uDQoNCnByZWMuY3ViZSA8LSBnZXRDREYuZGF0YShmaWxlUGF0aCA9IGZpbGUucHJlYywNCiAgICAgICAgICAgICAgICAgICAgICAgICBsb25SYW5nZSA9IGMoMjIzLjUsMzAxLjUpLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgbGF0UmFuZ2UgPSBjKDE3LjI1LDU1LjUpLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgdGltZVJhbmdlID0gYygwLCAxNDI0NCksDQogICAgICAgICAgICAgICAgICAgICAgICAgYXNDdWJlID0gVFJVRSkNCg0KcHJlYy5ieS55ZWFyIDwtIGFycmF5KGRpbSA9IGMoMTA1LCA1MiwgMzkpKQ0KDQpmb3IgKGkgaW4gMTk3OToyMDE3KSB7DQogIGJlZ2luIDwtIHBhc3RlKGksIDEsIDEsIHNlcCA9ICItIikNCiAgZW5kIDwtIHBhc3RlKGksIDEyLCAzMSwgc2VwID0gIi0iKQ0KICANCiAgIyMgSGVscGVyIGZ1bmN0aW9uIHRvIGdldCAiZGF5cyBzaW5jZSIgcmFuZ2UgIA0KICByYW5nZSA8LSBnZXRUaW1lUmFuZ2UoYmVnaW4sIGVuZCwgIjE5NzktMDEtMDEiKSAgIA0KICANCiAgcHJlYy5ieS55ZWFyWywgLCBpIC0gMTk3OSArIDFdIDwtIGFwcGx5KHByZWMuY3ViZSRtZXRzJFBSRUNbLCAsIHJhbmdlWzFdOnJhbmdlWzJdXSwgMToyLCBzdW0pDQp9DQoNCiMjIEFkZCBkaW1lbnNpb24gbmFtZXMgZm9yIHJlYWRhYmlsaXR5DQpkaW1uYW1lcyhwcmVjLmJ5LnllYXIpIDwtIGxpc3QocHJlYy5jdWJlJGRpbXMkbG9uLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZWMuY3ViZSRkaW1zJGxhdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAxOTc5OjIwMTcpDQoNCiMjIFJldHJpZXZlIHRvdGFsIFBSRUMgZm9yIDE5ODgNCnByZWMuYnkueWVhclsxOjYsIDE6NiAsICIxOTg4Il0gJT4lIGhlYWQoKQ0KYGBgDQoNCldoYXQgYWJvdXQgbW9udGhseSB0b3RhbCBwcmVjaXBpdGF0aW9uPw0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShsdWJyaWRhdGUpDQoNCnByZWMuYnkubW9udGggPC0gYXJyYXkoZGltID0gYygxMDUsIDUyLCA0NjgpKQ0KDQpmb3IgKGkgaW4gMTo0NjgpIHsNCiAgeWVhciA8LSBjZWlsaW5nKGkgLyAxMikgLSAxDQogIG1vbnRoIDwtIGlmZWxzZShpICUlIDEyID09IDAsIDEyLCBpICUlIDEyKQ0KICANCiAgYmVnaW4gPC0gcGFzdGUoeWVhciArIDE5NzksDQogICAgICAgICAgICAgICAgIG1vbnRoLCAxLCANCiAgICAgICAgICAgICAgICAgc2VwID0gIi0iKQ0KICANCiAgZW5kIDwtIHBhc3RlKHllYXIgKyAxOTc5LCANCiAgICAgICAgICAgICAgIG1vbnRoLCBkYXlzX2luX21vbnRoKGFzLkRhdGUocGFzdGUoeWVhciArIDE5NzksIG1vbnRoLCAxLCBzZXAgPSAiLSIpKSksIA0KICAgICAgICAgICAgICAgc2VwID0gIi0iKQ0KDQogIHJhbmdlIDwtIGdldFRpbWVSYW5nZShiZWdpbiwgZW5kLCAiMTk3OS0wMS0wMSIpIA0KICANCiAgIyMgU3RvcmUgbW9udGhseSBwcmVjaXBpdGF0aW9uIGRhdGENCiAgcHJlYy5ieS5tb250aFssICwgaV0gPC0gYXBwbHkocHJlYy5jdWJlJG1ldHMkUFJFQ1ssICwgcmFuZ2VbMV06cmFuZ2VbMl1dLCAxOjIsIHN1bSkNCn0NCg0KZGltbmFtZXMocHJlYy5ieS5tb250aCkgPC0gbGlzdChwcmVjLmN1YmUkZGltcyRsb24sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZWMuY3ViZSRkaW1zJGxhdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMTo0NjgpDQoNCiMjIFJldHJpZXZlIG1vbnRoIHRvdGFsIFBSRUMgZm9yIE1heSAxOTg4DQpwcmVjLmJ5Lm1vbnRoWzE6NiwgMTo2ICwgKDE5ODggLSAxOTc5KSAqIDEyICsgNV0gJT4lIGhlYWQoKQ0KDQpybShwcmVjLmN1YmUpOyBnYygpDQpgYGANCg0KXG5ld3BhZ2UNCg0KTGV0cyBwbG90IGEgcmFzdGVyIG1hcCBvZiAxOTg4IGZvciBhdmVyYWdlIG1heGltdW0gdGVtcGVyYXR1cmUgYW5kIHRvdGFsIHByZWNpcGl0YXRpb24uDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoZ2d0aGVtZXMpDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KDQojIyBDb252ZXJ0IGFycmF5cyBpbnRvIGxvbmcgZm9ybSBmb3IgcGxvdHRpbmcNCnByZWMubG9uZyA8LSBwcmVjLmJ5LnllYXIgJT4lIG1lbHQoKSAlPiUgcmVuYW1lKGxvbiA9IFZhcjEsIGxhdCA9IFZhcjIsIHllYXIgPSBWYXIzLCB0b3RhbC5wcmVjID0gdmFsdWUpICANCm1heHQubG9uZyA8LSBtYXh0LmJ5LnllYXIgJT4lIG1lbHQoKSAlPiUgcmVuYW1lKGxvbiA9IFZhcjEsIGxhdCA9IFZhcjIsIHllYXIgPSBWYXIzLCBtZWFuLm1heHQgPSB2YWx1ZSkNCg0KZ2dwbG90KCkgKw0KICBnZW9tX3Jhc3RlcihkYXRhID0gbWF4dC5sb25nICU+JSBmaWx0ZXIoeWVhciA9PSAxOTg4KSwNCiAgICAgICAgICAgICAgYWVzKHg9bG9uLTM2MCx5PWxhdCxmaWxsPW1lYW4ubWF4dCksIGludGVycG9sYXRlID0gVFJVRSkgKw0KICBnZW9tX2NvbnRvdXIoZGF0YSA9IHByZWMubG9uZyAlPiUgZmlsdGVyKHllYXIgPT0gMTk4OCksIA0KICAgICAgICAgICAgICAgYWVzKHg9bG9uLTM2MCx5PWxhdCx6PXRvdGFsLnByZWMpLCBjb2xvcj0id2hpdGUiLCBhbHBoYSA9IDAuMzMpICsNCiAgbGFicyhmaWxsID0gIk1BWFQiLA0KICAgICAgIHRpdGxlID0gIk1lYW4gTUFYVCBhbmQgVG90YWwgUFJFQyAtIDE5ODgiKSArDQogIGNvbnVzLm1hcCgpICsNCiAgdGhlbWVfbWFwKCkNCg0KYGBgDQoNCioqKg0KDQojIyMjIFByZWNpcGl0YXRpb24gKFBSRUMpIEV4cGxvcmF0aW9uIHdpdGggVGltZS1EZXBlbmRlbmNlDQoNCkhlcmUgaXMgd2hlcmUgSSB3aWxsIHRyeSB0byByZWR1Y2UgdGhlIGRpbWVuc2lvbnMgb2Ygb3VyIGRhdGFzZXQgYnkgZml0dGluZyBhIHRpbWUgc2VyaWVzIG1vZGVsIGF0IGVhY2ggc3BhdGlhbCBsb2NhdGlvbiAobG9uLCBsYXQpIGZyb20gMTk3OSB0byAyMDE3IG9mIG1vbnRobHkgdG90YWwgcHJlY2lwaXRhdGlvbiBhbmQgc2VhcmNoIGZvciBpcnJlZ3VsYXJpdGllcyBpbiB0aGUgZGF0YSBieSB3YXkgb2Ygb3V0bGllcnMuIEkgd2lsbCBzdGFuZGFyZGl6ZSB0aGUgcmVzaWR1YWxzIGFuZCBjb21wYXJlIHRoZSBlbnRpcmUgc3BhdGlhbC10ZW1wb3JhbCBkYXRhIGZvciBvdXRsaWVycy4gVGhpcyB3aWxsIGlkZW50aWZ5IHJlZ2lvbnMgb2YgYW5vbWFsaWVzIHNsaWNlZCBieSB0aW1lIChtb250aHMpLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KbGlicmFyeShmb3JlY2FzdCkNCg0KZmlsZS5wcmVjIDwtICIuLi8uLi9kYXRhL2VyYS1pbnRlcmltL1BSRUMubmMiICMgUHJlY2lwaXRhdGlvbg0KDQpwcmVjLmN1YmUgPC0gZ2V0Q0RGLmRhdGEoZmlsZVBhdGggPSBmaWxlLnByZWMsDQogICAgICAgICAgICAgICAgICAgICAgICAgbG9uUmFuZ2UgPSBjKDIyMy41LDMwMS41KSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGxhdFJhbmdlID0gYygxNy4yNSw1NS41KSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVSYW5nZSA9IGMoMCwgMTQyNDQpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGFzQ3ViZSA9IFRSVUUpDQoNCiMjIEFycmF5IHRvIHN0b3JlIHJlc2lkdWFscyBmcm9tIHRpbWUtc2VyaWVzIGRlY29tcG9zaXRpb24NCnByZWMubW9udGgudHMgPC0gYXJyYXkoZGltID0gYygxMDUsIDUyLCA0NjgpKQ0KDQojIyBBZGQgZGltZW5zaW9uIG5hbWVzIGZvciByZWFkYWJpbGl0eQ0KZGltbmFtZXMocHJlYy5tb250aC50cykgPC0gbGlzdChwcmVjLmN1YmUkZGltcyRsb24sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZWMuY3ViZSRkaW1zJGxhdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMTo0NjgpDQoNCnJtKHByZWMuY3ViZSkNCmdjKCkNCg0KZm9yIChsb24gaW4gMToxMDUpIHsNCiAgZm9yIChsYXQgaW4gMTo1Mikgew0KICAgICMjIEZvcm1hdCBkYXRhIGludG8gdGltZS1zZXJpZXMNCiAgICB0cy5kYXRhIDwtIHRzKHByZWMuYnkubW9udGhbbG9uLCBsYXQsIF0sIHN0YXJ0ID0gYygxOTc5LCAxKSwgZnJlcXVlbmN5ID0gMTIpDQogICAgDQogICAgIyMgRGVjb21wb3NlIHRpbWUtc2VyaWVzIGludG8gc2Vhc29uYWwsIHRyZW5kLCBhbmQgcmFuZG9tIGNvbXBvbmVudHMNCiAgICB0cy5tb2RlbCA8LSBzdGwodHMuZGF0YSwgcy53aW5kb3cgPSAicGVyaW9kaWMiKQ0KDQogICAgIyMgUmV0cmlldmUgbW9kZWwgcmVzaWR1YWxzIGFuZCBzdG9yZSBpbiBhcnJheQ0KICAgIHByZWMubW9udGgudHNbbG9uLCBsYXQsIF0gPC0gcmVtYWluZGVyKHRzLm1vZGVsKSAlPiUgYXMudmVjdG9yKCkNCiAgfQ0KfQ0KDQojIyBTdGFuZGFyZCBkZXZpYXRpb24gZm9yIHNwYXRpYWwgcG9pbnRzIGFjcm9zcyB0aW1lDQpwcmVjLm1vbnRoLnRzLnNkIDwtIGFwcGx5KHByZWMubW9udGgudHMsIDE6Miwgc2QpDQoNCiMjIFJlc2NhbGUgYWxsIHJlc2lkdWFscw0KZm9yIChsb24gaW4gMToxMDUpIHsNCiAgZm9yIChsYXQgaW4gMTo1Mikgew0KICAgIHByZWMubW9udGgudHNbbG9uLCBsYXQsIF0gPC0gcHJlYy5tb250aC50c1tsb24sIGxhdCwgXSAvIHByZWMubW9udGgudHMuc2RbbG9uLCBsYXRdDQogIH0NCn0NCg0KcHJlYy5tb250aC50cy5zZC5sb25nIDwtIHByZWMubW9udGgudHMgJT4lIG1lbHQoKSAlPiUgcmVuYW1lKGxvbiA9IFZhcjEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhdCA9IFZhcjIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWUgPSBWYXIzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZC5yZXNpZHVhbCA9IHZhbHVlKQ0KDQojIyBJbnRlcmVzdGVkIGluIHJlc2lkdWFscyB0aGF0IGFyZSBncmVhdGVyIHRoYW4gMyBzZCBvZiB0aGUgc3BhdGlhbC10ZW1wb3JhbCBkYXRhDQpvdXRsaWVycyA8LSBwcmVjLm1vbnRoLnRzLnNkLmxvbmcgJT4lIGZpbHRlcihhYnMoc2QucmVzaWR1YWwpID4gMykNCg0Kb3V0bGllcnMgJT4lIGhlYWQoKQ0KDQpgYGANCg0KTGV0IHNlZSBpZiBvdXIgZWZmb3J0cyBjYXB0dXJlZCBhbnkga25vd24gYW5vbWFsaWVzLiBJIHdpbGwgcGxvdCBvdXRsaWVycyBmcm9tIDIwMTIgYW5kIHNlZSBpZiB3ZSBjYW4gInNlZSIgSHVycmljYW5lIFNhbmR5Lg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShnZ3RoZW1lcykNCg0KIyMgKDEyKjE6MzkpIC0gMTINCm91dGxpZXJzIDwtIG91dGxpZXJzICU+JSBtdXRhdGUoeWVhciA9IGNlaWxpbmcodGltZSAvIDEyKSAtIDEgKyAxOTc5LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9udGggPSBpZmVsc2UodGltZSAlJSAxMiA9PSAwLCAxMiwgdGltZSAlJSAxMikpDQoNCm91dGxpZXJzLjIwMTIgPC0gb3V0bGllcnMgJT4lIGZpbHRlcih5ZWFyID09IDIwMTIpDQoNCiMjIEhlbHBlciBmdW5jdGlvbiB0byBkZWNpZGUgbnVtYmVyIG9mIGNsdXN0ZXJzLiBTaW1pbGlhciB0byBhIHNjcmVlIHBsb3QNCndzc3Bsb3Qob3V0bGllcnMuMjAxMlssIGMoImxvbiIsICJsYXQiKV0pIA0KDQojIyBDcmVhdGUgYXJiaXRyYXJ5IGNsdXN0ZXJzIGJhc2VkIG9uIHNwYXRpYWwgcG9pbnRzLiBNb3JlIGNsdXN0ZXJzIHJlc3VsdHMgaW4gc21hbGxlcg0KIyMgc3BhdGlhbCByZWdpb25zIGFuZCBsZXNzIGNsdXN0ZXJzIHJlc3VsdHMgaW4gbGFyZ2VyIHNwYXRpYWwgcmVnaW9ucw0Kc2V0LnNlZWQoMTIzNCkNCmNsdXN0ZXIgPC0ga21lYW5zKG91dGxpZXJzLjIwMTJbLCBjKCJsb24iLCAibGF0IildLCBjZW50ZXJzID0gNSwgbnN0YXJ0ID0gMjUpDQoNCiMjIEFzc2lnbiBzcGF0aWFsIHBvaW50cyB0byBjbHVzdGVyDQpvdXRsaWVycy4yMDEyJGNsdXN0ZXIgPC0gY2x1c3RlciRjbHVzdGVyDQoNCiMjIFJldHJpZXZlIGNlbnRlciBvZiBjbHVzdGVyDQpjZW50ZXJzIDwtIGNsdXN0ZXIkY2VudGVycyAlPiUgYXMuZGF0YS5mcmFtZSgpDQoNCmdncGxvdChkYXRhID0gb3V0bGllcnMuMjAxMiwgYWVzKHggPSBsb24gLSAzNjAsIHkgPSBsYXQpKSArDQogIHN0YXRfZGVuc2l0eTJkKGFlcyhmaWxsID0gZmFjdG9yKGNsdXN0ZXIpLCBncm91cCA9IGZhY3RvcihjbHVzdGVyKSksIGdlb20gPSAicG9seWdvbiIpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBzZC5yZXNpZHVhbCksIHNpemUgPSA0KSArDQogIGdlb21fbGFiZWwoZGF0YSA9IGNlbnRlcnMsIA0KICAgICAgICAgICAgIGFlcyh4ID0gbG9uIC0gMzYwLCB5ID0gbGF0LCBsYWJlbCA9IDE6NSksIA0KICAgICAgICAgICAgIHNpemUgPSA2KSArDQogIHNjYWxlX2NvbG9yX2Rpc3RpbGxlcihwYWxldHRlID0gIlNwZWN0cmFsIikgKw0KICBsYWJzKGZpbGwgPSAiQ2x1c3RlciIsDQogICAgICAgY29sb3IgPSAiU3RhbmRhcmRpemVkIFJlc2lkdWFscyIsDQogICAgICAgdGl0bGUgPSAiQ2x1c3RlcihzKSBvZiBBbm9tYWxpZXMgLSAyMDEyIikgKw0KICBjb251cy5tYXAoKSArDQogIHRoZW1lX21hcCgpDQoNCmBgYA0KDQpGcm9tIHRoZSBtYXAgYWJvdmUsIGl0IGxvb2tzIGxpa2UgY2x1c3RlciAyIGNvdWxkIGJlIHRoZSByZXN1bHQgb2YgSHVycmljYW5lIFNhbmR5IGxlYXZpbmcgYmVoaW5kIGFuIHVudXN1YWwgYW1vdW50IG9mIHByZWNpcGl0YXRpb24uIEtlZXAgaW4gbWluZCB0aGF0IHRoZSBtYXAgcGxvdHRlZCBhbm9tYWxpZXMgZm9yIHRoZSBlbnRpcmUgeWVhciAtIHBvc3NpYmx5IGFub21hbGllcyBiZWZvcmUgU2FuZHkuIFNpbmNlIG91ciBhbmFseXNpcyBoYXMgYmVlbiBvbiBhIG1vbnRobHkgc2NhbGUsIGxldHMgdGFrZSBhIGxvb2sgb2YgZWFjaCBtb250aCBmb3IgMjAxMi4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmdncGxvdChkYXRhID0gb3V0bGllcnMuMjAxMiwgYWVzKHggPSBsb24gLSAzNjAsIHkgPSBsYXQpKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gc2QucmVzaWR1YWwsIHBjaCA9IGZhY3RvcihjbHVzdGVyKSksIHNpemUgPSA0LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIHNjYWxlX2NvbG9yX2Rpc3RpbGxlcihwYWxldHRlID0gIlNwZWN0cmFsIikgKw0KICBmYWNldF93cmFwKH5tb250aCkgKw0KICBjb251cy5tYXAoKSArDQogIGxhYnModGl0bGUgPSAiQ2x1c3RlcihzKSBvZiBBbm9tYWxpZXMgYnkgTW9udGggKDIwMTIpIikgKw0KICB0aGVtZV9tYXAoKQ0KDQojIyBPY3RvYmVyIDIwMTINCmdncGxvdChkYXRhID0gb3V0bGllcnMuMjAxMiAlPiUgZmlsdGVyKG1vbnRoID09IDEwKSwgYWVzKHggPSBsb24gLSAzNjAsIHkgPSBsYXQpKSArDQogIGdlb21fdGlsZShhZXMoZmlsbCA9IHNkLnJlc2lkdWFsKSwgc2l6ZSA9IDQpICsNCiAgZ2VvbV9sYWJlbChkYXRhID0gY2VudGVycywgDQogICAgICAgICAgICAgYWVzKHggPSBsb24gLSAzNjAsIHkgPSBsYXQsIGxhYmVsID0gMTo1KSwgDQogICAgICAgICAgICAgc2l6ZSA9IDYpICsNCiAgc2NhbGVfZmlsbF9kaXN0aWxsZXIocGFsZXR0ZSA9ICJSZWRzIikgKw0KICBjb251cy5tYXAoKSArDQogIGxhYnMoeCA9ICJMb25naXR1ZGUiLA0KICAgICAgIHkgPSAiTGF0aXR1ZGUiLA0KICAgICAgIGZpbGwgPSAiU3RhbmRhcmRpemVkIFJlc2lkdWFscyIsDQogICAgICAgdGl0bGUgPSAiQ2x1c3RlcihzKSBvZiBBbm9tYWxpZXMsIE9jdG9iZXIgMjAxMiIpICsNCiAgdGhlbWVfbWFwKCkNCmBgYA0KDQpUaGVyZSBpdCBpcyEgQXQgdGltZSA0MDYgKE9jdG9iZXIgMjAxMikgd2UgY2FwdHVyZWQgSHVycmljYW5lIFNhbmR5J3MgdW51c3VhbCBhbW91bnQgb2YgcHJlY2lwaXRhdGlvbiBhbmQgYWNjb3JkaW5nIHRvIG15IEdvb2dsZSBzZWFyY2ggSHVycmljYW5lIFNhbmR5IG9mZmljaWFsbHkgbWFkZSBsYW5kZmFsbCBPY3RvYmVyIDI5LCAyMDEyLiBJIGNoZWNrZWQgdGhlIHBhdGggSHVycmljYW5lIFNhbmR5IHRvb2sgYW5kIGl0IHdhcywgYWNjb3JkaW5nIHRvIG15IG1hcCwgZnJvbSBjbHVzdGVyIDUgaHVnZ2luZyB0aGUgZWFzdGNvYXN0IHRvIGNsdXN0ZXIgMi4gWW91IGNhbiBzZWUgdGhhdCBIdXJyaWNhbmUgU2FuZHkgZHVtcGVkIHRyZW1lbmRvdXMgYW1vdW50cyBvZiByYWluIG92ZXIgY2x1c3RlciA1IHdvcmtpbmcgaXRzIHdheSB1cCB0b3dhcmRzIGNsdXN0ZXIgMiBhbmQgbm90IGR1bXBpbmcgYW55IHVudXN1YWwgYW1vdW50IG9mIHByZWNpcGl0YXRpb24gaW4gYmV0d2Vlbi4gSSB3b3VsZCBndWVzcyB0aGlzIGlzIHdoZXJlIFNhbmR5IHBpY2tlZCB1cCBlbmVyZ3ksIHVsdGltYXRlbHkgZHVtcGluZyBldmVyeXRoaW5nIGl0IGhhZCBvbiBOZXcgSmVyc2V5LiANCg0KQXMgZGVtb25zdHJhdGVkIGFib3ZlLCB3ZSB3ZXJlIGFibGUgdG8gY2FwdHVyZSBhbiB1bnVzdWFsIGV2ZW50LCBIdXJyaWNhbmUgU2FuZHksIGJ5IHdheSBvZiBhbm9tYWx5IGRldGVjdGlvbi4gTXkgbmV4dCBxdWVzdGlvbiB3b3VsZCBiZSwgd2hhdCBvdGhlciBrbm93biBhbm9tYWxpZXMgZGlkIHdlIHBpY2sgdXA/IE9yIGFuIGV2ZW4gbW9yZSBpbnRlcmVzdGluZyBxdWVzdGlvbiB3b3VsZCBiZSB3aGF0IG90aGVyIGFub21hbGllcyBkaWQgd2UgcGljayB1cCB0aGF0IGlzIHVua25vd24/IFdoYXQgZWZmZWN0IGRpZCB0aGlzIHVua25vd24gYW5vbWFseSBoYXZlIG9uIHRoZSByZWdpb24/IFdhcyB0aGlzIHVua25vd24gZXZlbnQgcmFyZT8gT3IgZG9lcyB0aGlzIHVua25vd24gZXZlbnQgcmVwZWF0IG9uIGEgY3ljbGU/IA0KDQpCeSBmaW5kaW5nIHRoZXNlIGV4dHJlbWVzIGluIHRoZSBkYXRhLCB3ZSBjYW4gdXNlIEpEJ3Mgc21vb3RoaW5nIG1vZGVsIHdpdGggYWRkaXRpb25hbCBkYXRhLCBmb3IgZXhhbXBsZSwgd2F0ZXIgcXVhbGl0eSBmb3IgdGhhdCByZWdpb24gYW5kIGFuYWx5emUgaG93IHRoZXNlIGV4dHJlbWUgZXZlbnRzIGVmZmVjdCB3YXRlciBxdWFsaXR5IGF0IHRoYXQgcmVnaW9uLiBDYW4gd2Ugc2hvdyB0aGF0IHVudXN1YWxseSBsb3cgYW1vdW50cyBvZiBwcmVjaXBpdGF0aW9uIChkcm91Z2h0KSBuZWdhdGl2ZWx5IGVmZmVjdCB3YXRlciBxdWFsaXR5IGJ5IGNvbmNlbnRyYXRpbmcgY29udGFtaW5hbnRzIGFuZCBvdGhlciBwYXJ0aWNsZXM/IE9yIGhvdyBhYm91dCB0aGUgb3RoZXIgZXh0cmVtZT8gV2hhdCBjYW4gd2Ugc2F5IGFib3V0IGEgcmVnaW9uIHRoYXQgaXMgc2F0dXJhdGVkIGZyb20gcHJlY2lwaXRhdGlvbj8gVWx0aW1hdGVseSwgSSBiZWxpZXZlIHRoZSBxdWVzdGlvbnMgd2UgYW5zd2VyIHRoYXQgZ2l2ZXMgdXMgdGhlIGJlc3QgY2hhbmNlIG9mIHdpbm5pbmcgYXJlIHF1ZXN0aW9ucyB0aGF0IGRpcmVjdGx5IGVmZmVjdCBub3JtYWwgcGVvcGxlJ3MgbGl2ZXMuIFF1ZXN0aW9ucyBhbmQgYW5zd2VycyB0aGF0IGV2ZXJ5b25lIGNhbiByZWxhdGUgdG8uIA0KDQoqKioNCg0KIyMjIyBSZWdpb25zIG9mIEludGVyZXN0IGZvciBJbnZlc3RpZ2F0aW9uDQoNClRoZSBmb2xsb3dpbmcgbWFwIEkgYW0gcGxvdHRpbmcgY29sbGFwc2VzIGFsbCB0aGUgcHJldmlvdXNseSBkZXRlY3RlZCBvdXRsaWVycyBvbnRvIGEgc2luZ2xlIHR3by1kaW1lbnNpb25hbCBtYXAuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpvdXRsaWVycy5zdW1tYXJ5IDwtIG91dGxpZXJzICU+JSBncm91cF9ieShsb24sIGxhdCkgJT4lIHN1bW1hcmlzZShhbm9tYWxpZXMgPSBuKCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVhbiA9IG1lYW4oc2QucmVzaWR1YWwpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heCA9IG1heChhYnMoc2QucmVzaWR1YWwpKSkNCnBsb3Quc3VtbWFyeSA8LQ0KICBnZ3Bsb3QoZGF0YSA9IG91dGxpZXJzLnN1bW1hcnksIGFlcyh4ID0gbG9uIC0gMzYwLCB5ID0gbGF0KSkgKw0KICBnZW9tX3Jhc3RlcihhZXMoZmlsbCA9IG1lYW4pLCBpbnRlcnBvbGF0ZSA9IFRSVUUpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IGFub21hbGllcywgc2l6ZSA9IGZhY3Rvcihhbm9tYWxpZXMpKSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBzY2FsZV9zaXplX21hbnVhbCh2YWx1ZXMgPSBleHAoc2VxKDAuNzUsIDIuMjUsIGxlbmd0aC5vdXQgPSAxNSkpIC0gMSkgKw0KICBzY2FsZV9maWxsX2Rpc3RpbGxlcihwYWxldHRlID0gIlJkQnUiKSArDQogIGxhYnMoeCA9ICJMb25naXR1ZGUiLA0KICAgICAgIHkgPSAiTGF0aXR1ZGUiLA0KICAgICAgIGZpbGwgPSAiTWVhbiBSZXNpZHVhbCAoc3RyZW5ndGgpIiwNCiAgICAgICB0aXRsZSA9ICJBbm9tYWx5IENvdW50IGFuZCBTdHJlbmd0aCAoMTk3OS0yMDE3KSIpICsNCiAgY29udXMubWFwKCkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KZ2dzYXZlKCJhbm9tYWxpZXMtcGxvdC5qcGciLCBwbG90LnN1bW1hcnksIGhlaWdodCA9IDIwLCB3aWR0aCA9IDMwLCB1bml0cyA9ICJpbiIpDQpgYGANCg0KIVthbm9tYWxpZXMtcGxvdC5qcGddKGFub21hbGllcy1wbG90LmpwZykNCg0KKioqDQoNClRoZSBtYXAgaXMgaGFyZCB0byBzZWUgd2hlbiBvdXRwdXRlZCBpbiB0aGlzIGRvY3VtZW50LCBzbyBJIHNhdmVkIGl0IGFzIGFub21hbGllcy1wbG90LmpwZyBpbiB0aGlzIGZpbGUgZGlyZWN0b3J5Lg0KDQpIb3cgSSBpbnRlcnByZXQgdGhlIG1hcCBpcyBhcyBmb2xsb3dzOiB0aGUgbnVtYmVycyB5b3Ugc2VlIGlzIGEgc2ltcGxlIGNvdW50IG9mIGFub21hbGllcyBpbiB0aGUgMzkgeWVhcnMgYXQgdGhhdCBwYXJ0aWN1bGlhciBzcGF0aWFsIHBvaW50IGFuZCB0aGUgZGlmZmVyZW50IHNpemVzIG9mIHRoZSBudW1iZXJzIGp1c3QgY29ycmVzcG9uZCB0byB0aGUgY291bnQuIEl0IGhlbHBzIHVzIHNlZSBsYXJnZXIgbnVtYmVycyBvdmVyIHNtYWxsZXIgbnVtYmVycy4gVGhlIGNvbG9ycyBpbiB0aGUgcmFzdGVyIG1hcCBjYW4gYmUgaW50ZXJwcmV0ZWQgYXMgdGhlIG92ZXJhbGwgc3RyZW5ndGggb2YgdGhlIGFub21hbGllcy4gVGhlICJyZWRzIiB0ZWxscyB1cyB0aGF0IHdoZW4gYW4gYW5vbWFseSBvY2N1cnMsIHRoZSBhbm9tYWx5IHJlbGVhc2VzIHVudXN1YWxseSBoaWdoIGFtb3VudHMgb2YgcHJlY2lwaXRhdGlvbiBmb3IgdGhhdCBsb2NhdGlvbi4gVGhlICJibHVlcyIgdGVsbCB1cyB0aGUgb3Bwb3NpdGUsIGluZGljYXRpbmcgdGhlIGFub21hbGllcyBvdmVyIHRoZXNlIGxvY2F0aW9ucyByZWxlYXNlcyB1bnVzdWFsbHkgbG93IGFtb3VudHMgb2YgcHJlY2lwaXRhdGlvbi4gDQoNClRoZSBhYm92ZSBtYXAgaGFzIGFsb3QgZ29pbmcgb24gc28gSSB3aWxsIHRyeSB0byBzaW1wbGlmeSB0aGUgbWFwIGV2ZW4gbW9yZSBieSBjcmVhdGluZyBhcmJpdHJhcnkgcmFua3MgZm9yIGVhY2ggb2YgdGhlIHNwYXRpYWwgcG9pbnRzIGFzIGEgd2VpZ2h0ZWQgYWdncmVncmF0ZSBvZiBhbm9tYWxpZXMsIG1lYW4gcmVzaWR1YWwgKHN0cmVuZ3RoKSwgYW5kIG1heCByZXNpZHVhbC4gVGhlIHdlaWdodHMgYXJlIGFzIGZvbGxvd3M6IGFub21hbGllcyA9IDAuNjAsIHN0cmVuZ3RoID0gMC4zMCwgYW5kIG1heCBzdHJlbmd0aCA9IDAuMTAuIA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kb3V0bGllcnMuc3VtbWFyeSA8LSBvdXRsaWVycy5zdW1tYXJ5ICU+JSBtdXRhdGUocmFuayA9IGFub21hbGllcyAqIDAuNjAgKyBhYnMobWVhbikgKiAwLjMwICsgYWJzKG1heCkgKiAwLjEwKQ0KDQpzcGF0aWFsLnBvaW50cyA8LSBwcmVjLm1vbnRoLnRzLnNkLmxvbmcgJT4lIGdyb3VwX2J5KGxvbiwgbGF0KSAlPiUgbmVzdCgpICU+JSBzZWxlY3QobG9uLCBsYXQpIA0KYW5vbWFseS5wb2ludHMgPC0gb3V0bGllcnMuc3VtbWFyeSAlPiUgZ3JvdXBfYnkobG9uLCBsYXQpICU+JSBuZXN0KCkgJT4lIHNlbGVjdChsb24sIGxhdCkNCnZvaWQgPC0gYW50aV9qb2luKHNwYXRpYWwucG9pbnRzLCBhbm9tYWx5LnBvaW50cykNCg0KZ2dwbG90KGRhdGEgPSBvdXRsaWVycy5zdW1tYXJ5LCBhZXMoeCA9IGxvbiAtIDM2MCwgeSA9IGxhdCkpICsNCiAgZ2VvbV9yYXN0ZXIoYWVzKGZpbGwgPSByYW5rKSwgaW50ZXJwb2xhdGUgPSBUUlVFKSArDQogIGdlb21fdGlsZShkYXRhID0gdm9pZCwgZmlsbCA9ICJibGFjayIpICsNCiAgc2NhbGVfZmlsbF9kaXN0aWxsZXIocGFsZXR0ZSA9ICJSZWRzIikgKw0KICBsYWJzKHggPSAiTG9uZ2l0dWRlIiwNCiAgICAgICB5ID0gIkxhdGl0dWRlIiwNCiAgICAgICBmaWxsID0gIlJhbmsiLA0KICAgICAgIHRpdGxlID0gIlNwYXRpYWwgQW5vbWFseSBTdHJlbmd0aCBieSBSYW5rICgxOTc5LTIwMTcpIikgKw0KICBjb251cy5tYXAoKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpgYGANCg0KQXQgbGFzdCwgd2UgYXJlIGd1aWRlZCBieSB0aGUgYnJpZ2h0IGFyZWFzIG9uIHRoZSBtYXAgYXMgcG90ZW50aWFsIGFyZWFzIG9mIGludGVyZXN0IGZvciBmdXJ0aGVyIHJlc2VhcmNoLiBGb3IgZXhhbXBsZSwgdGhlIGJvcmRlciBvZiBzb3V0aGVybiBDYWxpZm9ybmlhLCBOZXZhZGEsIEFyaXpvbmEsIGFuZCBCYWphIE1leGljbyBsaWdodHMgdXAgbGlrZSBhIGJlYWNvbi4gQW5vdGhlciByZWdpb24gaXMgY2VudHJhbCBNaXNzb3VyaSBvciBtb3N0IG9mIE1vbnRhbmEuIFRoZXJlIGFyZSBudW1lcm91cyBvdGhlciBsb2NhdGlvbnMgZm9yIHVzIHRvIGNvbnNpZGVyLiBUaGUgYmxhY2sgc3BvdHMgb24gdGhlIG1hcCBhcmUgdm9pZHMgKG5vIGRldGVjdGVkIGFub21hbHkpLg0KDQpBZnRlcndhcmRzLCBJIHRvb2sgdGhlICJTcGF0aWFsIEFub21hbHkgU3RyZW5ndGggYnkgUmFuayAoMTk3OS0yMDE3KSIgcGxvdCBhYm92ZSBhbmQgcG9zdCBwcm9jZXNzZWQgdGhlIGltYWdlIGJ5IGFkanVzdGluZyB0aGUgY29udHJhc3QgYW5kIGJyaWdodG5lc3MgbGV2ZWxzIHRvIGJyaW5nIHRoZSBicmlnaHQgc3BvdHMgb3V0LiBJIHNhdmVkIHR3byBwcm9jZXNzZWQgaW1hZ2VzIGluIHRoaXMgZmlsZSBkaXJlY3RvcnkgKGFub21hbHktbWFwLXByb2Nlc3NlZC0xIGFuZCBhbm9tYWx5LW1hcC1wcm9jZXNzZWQtMikNCg0KXG5ld3BhZ2UNCg0KIyMjIyMgUHJvY2Vzc2VkIEltYWdlcw0KDQohW2Fub21hbHktbWFwLXByb2Nlc3NlZC0xLmpwZ10oYW5vbWFseS1tYXAtcHJvY2Vzc2VkLTEuanBnKQ0KDQoqKioNCg0KIVthbm9tYWx5LW1hcC1wcm9jZXNzZWQtMi5qcGddKGFub21hbHktbWFwLXByb2Nlc3NlZC0yLmpwZykNCg0KSWYgd2Ugd2FudGVkIHRvLCB3ZSBjb3VsZCBzZXBlcmF0ZSB0aGUgYG91dGxpZXJzLnN1bW1hcnlgIGJ5IHVudXN1YWxseSBsb3cgYW5kIHVudXN1YWxseSBoaWdoIHByZWNpcGl0YXRpb24sIHJlLXJhbmsgdGhlIGFub21hbGllcywgYW5kIHJlcGxvdCB0aGUgaGVhdCBtYXAuDQoNCioqKg0KDQojIyMjIENvbmNsdXNpb24NCg0KVGhlIGdvYWwgSSBzZXQgb3V0IGF0IHRoZSBiZWdpbm5pbmcgb2YgdGhpcyByZXBvcnQgd2FzIHRvIHJlZHVjZSB0aGUgc3BhdGlhbCBkaW1lbnNpb24gb2Ygb3VyIGRhdGFzZXQgdG8gYWxsb3cgZm9yIHRhcmdldGVkIGFuYWx5c2lzLiBJIGJlbGlldmUgdGhlIGFuYWx5c2lzIGFib3ZlIHdpbGwgZ3VpZGUgdXMgaW4gc3Vic2V0dGluZyB0aGUgQ09OVVMgbWFwLiBBbiBhZnRlciB0aG91Z2h0IHRoYXQgSSB0aGluayB3b3VsZCBiZSBpbnNpZ2h0ZnVsIGlzIHRvIHN1cGVyaW1wb3NlIHRoZSAiU3BhdGlhbCBBbm9tYWx5IFN0cmVuZ3RoIGJ5IFJhbmsgKDE5NzktMjAxNykiIHBsb3Qgb3ZlciBhIHRlcnJhaW4gbWFwLCByaXZlcnMgYW5kIGxha2VzLCBuYXRpb25hbCBwYXJrcywgaGlnaHdheXMsIGNpdHksIGV0Yy4gVGhpcyBjb3VsZCBwcm92aWRlIHNvbWUgZ3VpZGFuY2UgaW4gcmVnaW9uIHNlbGVjdGlvbi4gVGhlIGFib3ZlIGFuYWx5c2lzIG9mIHRoZSBgUFJFQ2AgdmFyaWFibGUgY291bGQgYmUgZWFzaWx5IHN3YXBwZWQgb3V0IGZvciBgTUFYVGAgb3IgYE1JTlRgIGFuZCByZWFuYWx5emVkLiBJIGhvcGUgdGhpcyB3aWxsIGhlbHAgZ3VpZGUgdXMgbW92aW5nIGZvcndhcmQgd2l0aCB0YXJnZXRlZCBhbmFseXNpcyBhbmQgYWRkaXRpb25hbCBkYXRhIGNvbGxlY3Rpb24gZWZmb3J0cy4g